
#include <linux/spi/spidev.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <unistd.h>

#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

int rst_fd = -1;        /* 复位引脚，低电平复位 */
int dc_fd = -1;         /* 寄存器/数据 选择引脚（0：寄存器，1：数据） */
int spi_fd = -1;

int spi_init(const char *dev)
{
    int ret;
    uint8_t v8;
    uint32_t v32;

    do
    {
        spi_fd = open(dev, O_RDWR | O_NONBLOCK);
        if (spi_fd < 0)
        {
            printf("open(%s) failed, errno: %d\n", dev, errno);
            ret = -1;
            break;
        }

      #if 0
        v8 = SPI_MODE_2;
        ret = ioctl(spi_fd, SPI_IOC_RD_MODE, &v8);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_RD_MODE) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }
      #endif

        v8 = SPI_MODE_2;
        ret = ioctl(spi_fd, SPI_IOC_WR_MODE, &v8);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_WR_MODE) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }

      #if 0
        v8 = 0;
        ret = ioctl(spi_fd, SPI_IOC_RD_LSB_FIRST, &v8);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_RD_LSB_FIRST) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }
      #endif
        
        v8 = 0;
        ret = ioctl(spi_fd, SPI_IOC_WR_LSB_FIRST, &v8);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_WR_LSB_FIRST) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }
        
      #if 0
        v8 = 8;
        ret = ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &v8);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_RD_BITS_PER_WORD) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }
      #endif

        v8 = 8;
        ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &v8);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_WR_BITS_PER_WORD) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }
        
      #if 0
        v32 = 18000000;
        ret = ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &v32);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_RD_MAX_SPEED_HZ) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }
      #endif
        
        v32 = 40000000;
        ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &v32);
        if (ret < 0)
        {
            printf("ioctl(SPI_IOC_WR_MAX_SPEED_HZ) failed, ret: %d, errno: %d\n", ret, errno);
            break;
        }

        return 0;
    } while(0);

    close(spi_fd);
    spi_fd = -1;

    return ret;
}

void spi_uninit(void)
{
    close(spi_fd);
    return;
}

void spi_sendbytes(unsigned size, uint8_t *bytes)
{
	#define MAX_SPI_MSG_SIZE 4096
	
    int ret;
	while (size > MAX_SPI_MSG_SIZE)
	{
		struct spi_ioc_transfer xfer = {};
		xfer.tx_buf = (__u64)(intptr_t)bytes;
		xfer.rx_buf = 0;
		xfer.len = MAX_SPI_MSG_SIZE;

		ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &xfer);
		if (ret < 0)
		{
			printf("ioctl(SPI_IOC_MESSAGE) failed, ret: %d\n", ret);
		}
		
		size -= MAX_SPI_MSG_SIZE;
		bytes += MAX_SPI_MSG_SIZE;
	}
	
	if (size > 0)
	{
		struct spi_ioc_transfer xfer = {};
		xfer.tx_buf = (__u64)(intptr_t)bytes;
		xfer.rx_buf = 0;
		xfer.len = size;

		ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &xfer);
		if (ret < 0)
		{
			printf("ioctl(SPI_IOC_MESSAGE) failed, ret: %d\n", ret);
		}
	}
}

void gpio_init(void)
{
	system("echo 22 > /sys/class/gpio/export");
	system("echo out > /sys/class/gpio/gpio22/direction");

	system("echo 27 > /sys/class/gpio/export");
	system("echo out > /sys/class/gpio/gpio27/direction");
	
    rst_fd = open("/sys/class/gpio/gpio22/value", O_WRONLY | O_NONBLOCK);
    dc_fd = open("/sys/class/gpio/gpio27/value", O_WRONLY | O_NONBLOCK);

    write(rst_fd, "1", 1);
}

void gpio_uninit(void)
{
    close(rst_fd);
    close(dc_fd);
}

#define select_reg()   write(dc_fd, "0", 1)
#define select_data()   write(dc_fd, "1", 1)

void write_reg(uint8_t val)
{
    select_reg();
    spi_sendbytes(1, &val);
}

void _write_data(unsigned size, uint8_t *data)
{
    select_data();
    spi_sendbytes(size, data);
}

#define write_data(...) do { \
    uint8_t val[] = {__VA_ARGS__}; \
    _write_data(sizeof(val), val); \
} while(0)

/**************************************************/

#define LCD_W  240
#define LCD_H  240

struct st7789_dev
{
    uint16_t  id;           // LCD ID
    
    uint16_t  width;        // LCD 宽度
    uint16_t  height;       // LCD 高度
    uint8_t   rotation;     // 屏幕内容旋转方向，0-无旋转；1-90度旋转；2-180度旋转；3-270度旋转
    uint8_t   xoffset;
    uint8_t   yoffset;
};

struct st7789_dev lcd = {
    .id = 0,

    .width = LCD_W,
    .height = LCD_H,
    .rotation = 0,
    .xoffset = 0,
    .yoffset = 0
};

void lcd_reset(void)
{
  #if 0
    /* 硬reset */
    write(rst_fd, "0", 1);
    usleep(20000);
    write(rst_fd, "1", 1);
    usleep(20000);
  #endif

    /* 软reset */
    write_reg(0x01);
    usleep(10000);
}

void lcd_set_rotation(uint8_t r)
{
    lcd.rotation = r;
    switch(r)
    {
        /* 无旋转 */
        default:
        case 0:
        {
            lcd.width = LCD_W;
            lcd.height = LCD_H;	
            lcd.xoffset = 0;
            lcd.yoffset = 0;
            
            //BGR==1,MY==0,MX==0,MV==0
            write_reg(0x36);
            write_data(0);
            break;
        }
        /* 顺时针旋转90度 */
        case 1:
        {
            lcd.width = LCD_H;
            lcd.height = LCD_W;
            lcd.xoffset = 0;
            lcd.yoffset = 0;

            //BGR==1,MY==1,MX==0,MV==1
            write_reg(0x36);
			write_data((1<<6)|(1<<5));
            break;
        }
        /* 顺时针旋转180度 */
        case 2:
        {
            lcd.width = LCD_W;
            lcd.height = LCD_H;
            lcd.xoffset = 0;
            lcd.yoffset = 80;
            
            //BGR==1,MY==0,MX==0,MV==0
            write_reg(0x36);
			write_data((1<<6)|(1<<7));
            break;
        }
        /* 顺时针旋转270度 */
        case 3:
        {
            lcd.width = LCD_H;
            lcd.height = LCD_W;
            lcd.xoffset = 80;
            lcd.yoffset = 0;
            //BGR==1,MY==1,MX==0,MV==1
            write_reg(0x36);
			write_data((1<<7)|(1<<5));
            break;	
        }
    }
}

void lcd_set_window(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye)
{
	write_reg(0x2A);
	write_data((xs+lcd.xoffset)>>8, xs+lcd.xoffset, (xe+lcd.xoffset)>>8, xe+lcd.xoffset);

	write_reg(0x2B);	
	write_data((ys+lcd.yoffset)>>8, ys+lcd.yoffset, (ye+lcd.yoffset)>>8, ye+lcd.yoffset);

    write_reg(0x2C);
}

#define lcd_set_cursor(x, y)  lcd_set_window((x), (y), (x), (y))

void lcd_put_point(uint16_t x, uint16_t y, uint16_t v)
{
    lcd_set_cursor(x, y);
	_write_data(sizeof(v), (uint8_t*)&v);
}

void lcd_init(void)
{
    /* ST7789 复位/初始化 */
    lcd_reset();

    /* Memory Data Access Control */
    write_reg(0x36);
	write_data(0x00);

    /* Interface Pixel Format */
    write_reg(0x3A);
    write_data(0x55);

    /* Porch Setting */
    write_reg(0xB2);
	write_data(0x0C, 0x0C, 0x00, 0x33, 0x33);

    /* Gate Control */
    write_reg(0xB7);
    write_data(0x35);

    /* VCOM Setting */
    write_reg(0xBB);
    write_data(0x19);

    /* LCM Control */
    write_reg(0xC0);
    write_data(0x2C);

    /* VDV and VRH Command Enable */
    write_reg(0xC2);
    write_data(0x01);

    /* VRH Set */
    write_reg(0xC3);
    write_data(0x12);

    /* VDV Set */
    write_reg(0xC4);
    write_data(0x20);

    /* Frame Rate Control in Normal Mode */
    write_reg(0xC6);
    write_data(0x0F);

    /* Power Control 1 */
    write_reg(0xD0); 
    write_data(0xA4, 0xA1);

    /* Positive Voltage Gamma Control */
    write_reg(0xE0);
	write_data(0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23);

    /* Negative Voltage Gamma Control */
    write_reg(0xE1);
	write_data(0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23);

    /* Display Inversion On */
    write_reg(0x21);

    /* turn off sleep mode */
    write_reg(0x11); 
    //usleep(120000);

    /* Display On */
    write_reg(0x29);

    lcd_set_rotation(0);
}

uint16_t frame[LCD_W * LCD_H];

void lcd_clear()
{
    uint16_t y, x;

    lcd_set_window(0, 0, lcd.width-1, lcd.height-1);

    select_data();
	uint16_t *line = frame;
    for(y = 0; y < lcd.height; y++)
    {
		uint16_t *point = line;
		uint16_t color;
		if (y < 80)
		{
			color = 0xF800; // red
		}
		else if (y < 160)
		{
			color = 0x07E0; // grean
		}
		else
		{
			color = 0x001F; // grean
		}
        for(x = 0; x < lcd.width; x++)
        {
			*point = color;
			point++;
        }
		line += lcd.width;
    }
	_write_data(sizeof(frame), (uint8_t*)frame);
}

void hw_init(void)
{
    int ret;

    ret = spi_init("/dev/spidev0.0");
    if (ret < 0)
    {
        printf("spi_init() failed\n");
        return;
    }

    gpio_init();

    lcd_init();

    lcd_clear();
}

/**************************************************/

int main(int argc, char *argv[])
{
    hw_init();

    return 0;
}
